/**
 * 2017年5月21日
 */
package cn.edu.bjtu.workbench.configuration;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import org.deeplearning4j.nn.conf.layers.PoolingType;
import org.deeplearning4j.parallelism.ConcurrentHashSet;

import cn.edu.bjtu.workbench.api.ConfigChangeListener;
import cn.edu.bjtu.workbench.core.Deep4jModelType;
import cn.edu.bjtu.workbench.core.LoggerSupport;

/**
 * 当配置文件的最后修改时间变化,就会被线程检测到.
 * 提供一些set方法修改变量值并不会引发后台线程调用监听器
 * 
 * 只是为了方便以命令行方式运行的时候覆盖参数.
 * 
 * 自己开一个守护线程,负责监测文件的修改,并及时更新自己的变量
 * 一些路径，CNN 类型，以及训练 CNN 时的一些参数，比如句长，w2v 路径等
 * 
 * @author Alex
 *
 */
public class TextCategorizationCNNConfig extends LoggerSupport{
	private static final String sysPropKey = "configLoc";
	boolean externalFileConfig = false;
	private volatile long configFileLastModified = 0L;
	private static TextCategorizationCNNConfig inst = new TextCategorizationCNNConfig();
	public static TextCategorizationCNNConfig get(){
		return inst;
	}
	//重新读取配置文件载入模型,只要真正更新才返回true
	public synchronized boolean refresh(){
		try {
			Properties p = new Properties();
			if(externalFileConfig){
				String path  = System.getProperty(sysPropKey);
				File  f = new File(path);
				if(!f.exists()){
					throw new RuntimeException("check file specified by \"configLoc\" in launch params ");
				}	
				long lastM = f.lastModified();
				if(lastM != configFileLastModified){
					logger.warn("DETECT Changes");
					p.load(new FileInputStream(f));
					parse(p);
					configFileLastModified = lastM;
					return true;
				}
			}else{
				InputStream in = TextCategorizationCNNConfig.class.getClassLoader().getResourceAsStream("conf/cnnconfig.properties");
				p.load(in);
				parse(p);
			}
		} catch (FileNotFoundException e) {
			logger.error(e.getMessage());
		} catch (IOException e) {
			logger.error(e.getMessage());
		}
		return false;
	}

	
	/**
	 * 被refresh调用,refresh是同步的,所以这个不需要同步了
	 */
	private void parse(Properties p){
		//TODO:设置各个字段值
		this.WORD2VEC_MODEL_PATH = p.getProperty("WORD2VEC_MODEL_PATH");
		this.TEXT_DIR_OR_FILE = p.getProperty("TEXT_DIR_OR_FILE");
		this.CNN_NETWORK_SAVE_FILE = p.getProperty("CNN_NETWORK_SAVE_FILE");
		this.TEST_DIR_OR_FILE = p.getProperty("TEST_DIR_OR_FILE");
		this.CNN_EPOCH = Integer.parseInt(p.getProperty("CNN_EPOCH"));
		this.LABELS_FILE = p.getProperty("LABELS_FILE");
		this.cnnLayerFeatureMaps = Integer.parseInt(p.getProperty("CNN_LAYER_FEATURE_MAPS"));
		this.batchNumberToUpdate = Integer.parseInt(p.getProperty("CNN_BATCH_NUMBER"));
		this.MAX_SENTENCE_LENGTH = Integer.parseInt(p.getProperty("MAX_SENTENCE_LENGTH"));
		this.CNN_TYPE = p.getProperty("CNN_TYPE").equals("CG")?Deep4jModelType.ComputationGraph:Deep4jModelType.MultiLayerNetwork;
	}
	
	/**
	 * 这些方法一般用于命令行方式运行。来指定参数。
	 */
	public synchronized void setCNN_TYPE(Deep4jModelType type) {
		CNN_TYPE = type;
	}
	/**
	 * @param wORD2VEC_MODEL_PATH the wORD2VEC_MODEL_PATH to set
	 */
	public synchronized void setWORD2VEC_MODEL_PATH(String wORD2VEC_MODEL_PATH) {
		WORD2VEC_MODEL_PATH = wORD2VEC_MODEL_PATH;
	}
	/**
	 * @param tEXT_DIR_OR_FILE the tEXT_DIR_OR_FILE to set
	 */
	public synchronized void setTEXT_DIR_OR_FILE(String tEXT_DIR_OR_FILE) {
		TEXT_DIR_OR_FILE = tEXT_DIR_OR_FILE;
	}
	/**
	 * @param cNN_NETWORK_SAVE_FILE the cNN_NETWORK_SAVE_FILE to set
	 */
	public synchronized void setCNN_NETWORK_SAVE_FILE(String cNN_NETWORK_SAVE_FILE) {
		CNN_NETWORK_SAVE_FILE = cNN_NETWORK_SAVE_FILE;
	}
	/**
	 * @param tEST_DIR_OR_FILE the tEST_DIR_OR_FILE to set
	 */
	public synchronized void setTEST_DIR_OR_FILE(String tEST_DIR_OR_FILE) {
		TEST_DIR_OR_FILE = tEST_DIR_OR_FILE;
	}
	/**
	 * @param cNN_EPOCH the cNN_EPOCH to set
	 */
	public synchronized void setCNN_EPOCH(int cNN_EPOCH) {
		CNN_EPOCH = cNN_EPOCH;
	}
	//PROPERTIES****************************************************************************
	private int batchNumberToUpdate = 32;
	private String WORD2VEC_MODEL_PATH = "D:\\textdata\\mymodel\\dl4jModel_100.txt";
	private String TEXT_DIR_OR_FILE = "D:\\textdata\\wechatrawdata\\20170511153909.txt";
	private String CNN_NETWORK_SAVE_FILE = "D:\\textdata\\mymodel\\cnn.bin";
	private String TEST_DIR_OR_FILE = "D:\\textdata\\wechatrawdata\\20170511153909.txt";
	private int MAX_SENTENCE_LENGTH = 256;
	private int cnnLayerFeatureMaps = 100;      //Number of feature maps / channels / depth for each CNN layer
	private PoolingType globalPoolingType = PoolingType.MAX;
	private int CNN_EPOCH = 5;
	private Deep4jModelType CNN_TYPE;
	private String LABELS_FILE = "D:\\textdata\\labels.bin";
	//PROPERTIES*****************************************************************************
	public String getLabelsFile(){
		return LABELS_FILE;
	}
	
	public int getCNNLayerFeatureMaps(){
		return cnnLayerFeatureMaps;
	}
	public PoolingType getPoolingType(){
		return globalPoolingType;
	}
	
	public String getNetworkSaveFile(){
		return CNN_NETWORK_SAVE_FILE; 
	}
	
	public int getBathNumberToUpdate(){
		return batchNumberToUpdate;
	}
	public int getSentenceLength(){
		return MAX_SENTENCE_LENGTH;
	}
	public String getW2VModelPath(){
		return WORD2VEC_MODEL_PATH;
	}
	public String getDataSetDirOrFile(){
		return TEXT_DIR_OR_FILE;
	}
	public String getTestDataSetDirOrFile(){
		return TEST_DIR_OR_FILE;
	}
	public Date getLastModifiedTime(){
		return new Date(configFileLastModified);
	}
	public int getCNNEpoch(){
		return CNN_EPOCH;
	}

	
	public String getConfigFilePath(){
		try {
			return TextCategorizationCNNConfig.class.getResource("/conf/cnnconfig.properties").toURI().toString();
		} catch (URISyntaxException e) {
			return null;
		}
	}
	public Deep4jModelType getDeep4jModelType(){
		return CNN_TYPE;
	}
	
	
	public void addListener(ConfigChangeListener l){
		listeners.add(l);
	}
	
	private TextCategorizationCNNConfig(){
		externalFileConfig = System.getProperty(sysPropKey) != null;
		refresh();
		if(externalFileConfig)
			initWather();
	}
	//EXECUTORS ,一个监视线程,负责查看配置文件是不是被更改,10秒一检测,时间是在提交任务的时候指定的
	private ThreadFactory threadFactory = new ThreadFactory() {
		@Override
		public Thread newThread(Runnable r) {
			Thread  t = new Thread(r);
			t.setDaemon(true);
			t.setName("Dameon-Detect-Cnnconfig");
			return t;
		}
	};
	private final ScheduledExecutorService daemonWather = Executors.newSingleThreadScheduledExecutor(threadFactory);
	//当文件被更改的时候,监听器会得到通知,监听器方法调用是在后台守护线程里面
	private final ConcurrentHashSet<ConfigChangeListener> listeners = new ConcurrentHashSet<ConfigChangeListener>();
	//10秒检测一次是不是有变化
	private void initWather(){
		daemonWather.scheduleAtFixedRate(new WatchThread(), 10, 10, TimeUnit.SECONDS);
	}

	private class WatchThread implements Runnable{
			@Override
			public void run() {
				if(refresh()){
					logger.info("detect cnn file changes ,invoke listeners");
					for(ConfigChangeListener ccl:listeners){
						ccl.invoke(TextCategorizationCNNConfig.this);
					}
				}
			}
		}
	
}
